iT邦幫忙

2023 iThome 鐵人賽

DAY 17
1
自我挑戰組

從電子元件到傅立葉轉換 - 那些我有興趣的主題系列 第 17

[Day17] 你應該瞧瞧新的 React 官方文件

  • 分享至 

  • xImage
  •  

熟悉 React 開發者們,不知道有多久沒有看過官方文件了呢?自從 React hooks 推出,function component 成為主流之後,我看文件的次數也大幅減少了。這代表 React 的 API 相當成熟且容易上手,是好事一樁。不過有在密切追蹤 React 的開發者應該會知道,在幾年前 React 就開始籌備 beta 版的官方文件版(beta.reactjs.org),終於在 2023 年年初左右正式上線。連結

除了基礎教學之外,我覺得最值得一看的地方在最後一章節 – Escape Hatches,提到了很多在寫 React 會遇到的問題、常見的 anti-pattern 跟解決方法。

如果能仔細看完這個章節,理解並實際應用的話,大概也不需要去報名 React 線上課程了。

Synchronizing with Effects

這個章節描述了很多什麼是 effect,並且介紹如何正確使用 useEffect。如同官方文件所說的,side effect 是一種讓外部的事件、狀態與 React 的世界同步的方法。只有在這種時候使用 useEffect 最自然,也最不容易產生非預期的結果。在 React 開發模式當中,甚至會故意執行 useEffect 兩次來幫助你 debug。

  1. 沒有與外部的狀態或系統同步時,可能不需要 useEffect
  2. 盡可能加入 useEffect 使用到的變數到依賴(第二個參數當中),如果會造成無限迴圈的問題,很有可能是 useEffect 的用法錯誤
  3. 如果是由使用者的互動(click、scroll 等)所發起的 effect,直接把 effect 放在事件處理器就好,不用放在 useEffect
  4. useEffect 都是在渲染到螢幕之後才會執行,因此不需要太擔心 useEffect 會影響重繪效能
  5. 每次使用 useEffect 時,盡可能加入 cleanup 函數來避免元件 unmount 後可能產生的記憶體洩漏。

在這邊很重要的一點在於,不要把「useEffect 竟然會執行兩次當作是問題」,而是「要怎麼做才能讓 useEffect 就算執行兩次也沒有問題」。

You Might Not Need an Effect

在實際開發當中,我們應該避免使用 useEffect。這個章節寫了蠻多範例,幾乎涵蓋了大部分的使用場景。看似要用 useEffect,但其實完全可以避免的 pattern。在實際應用當中,每次寫到 useEffect 時,都可以想想是否有更好的寫法來避免 useEffect

computed prop

例如要計算某個依賴與其他狀態或 prop 的變數,與其直接寫在 useEffect,直接把計算搬出來即可,以官方的範例來說:

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');
  // ✅ Good: calculated during rendering
  const fullName = firstName + ' ' + lastName;
  // ...
}

如果計算比較複雜一點,我喜歡直接用 useMemo 將計算包起來:

function Form() {
  const [firstName, setFirstName] = useState('Taylor');
  const [lastName, setLastName] = useState('Swift');

  
  const fullName = useMemo(() => {
    return firstName + ' ' + lastName;
  }, [firstName, lastName]);
}

透過加入 key 來重置狀態

有時候想要在 userId 改變時重置狀態,這時候與其在 useEffect 寫重置的邏輯,直接在父元件裡加入 key={userId} 即可。 只要 React 偵測到 key 不同就會重新渲染。這樣就不用大費周章額外寫 useEffect 了。

fetch API

一般來說,呼叫 API 有兩種大分類:

  • 當元件初次渲染時獲取資料
  • 當某個狀態或 props 改變時重新獲取資料

舉例來說,直接在 useEffect 裡頭呼叫 fetch:

useEffect(() => {
  fetch('/api/xxx').then(...).then(data => setData(data))
  ...
}, [])

這樣子寫有幾個問題:

  • 元件要等到實際 render 後才會執行 useEffect,因此時機點稍微慢了一步
  • 沒辦法直接支援 SSR,因為 useEffect 一定要在 client 端執行
  • 如果這個元件是在特定 URL 渲染,那使用者頻繁切換頁面時可能會送出不必要的請求
    • 當請求完成但元件已經 unmount 時可能會跳出錯誤訊息:Can't perform a React state update on an unmounted component...

比較好的方法是:

  1. 直接用框架像是 Remix 或是 Next.js,直接在伺服器端拿資料
  2. 用其他 client data fetching 的函式庫像是 React QueryuseSWR 等等

以 React Query 來舉例,為了避免上述提到的問題,React Query 其實將整個 API fetch 的邏輯和 fetch 時的狀態管理都搬到 React 外部,並透過其他方式與 React 同步。這樣一來就不會被元件的生命週期所影響。除此之外 React Query 也能和 SSR 的框架搭配,初始資料也可以直接用同樣的 API 介面來存取。

Removing Effect Dependencies

官方建議將 react-hooks/exhaustive-deps 開啟,比較容易找到問題,大部分的情況都是在依賴有變化時想要重新執行 effect。

  • 如果依賴是一個 reactive 的值,通常加入到依賴列表裡面才是正確的
  • 刪除非必要的程式碼。useEffect 裡面儘可能只放跟依賴相關的程式碼

從 prop 傳過來的函數,在沒有用 useCallback 包住的情況下,每次傳過來的函數都會不一樣,因此加在依賴列表裡面會有造成不必要的 effect 執行。

目前只能暫時將它移除,或是再另外包一層 useCallback 確保他不會變化。最近 React 推出了新的 hook – useEffectEvent,方便在這種情況下大家可以直接用一個 hook 包起來,不需要額外放到依賴列表裡。

如果確定傳入的值不會有變化,直接將它放到元件外部以常數宣告也可以:

const options = {
  timeout: 5000,
}

function Chat() {
  useEffect(() => {
    const connection = createConnection(options);
    connection.connect()
    
    return () => connection.disconnect()
  }, [])
}

這樣子完全沒有問題,因為值不會有變化。

完成最後的小挑戰

在 Escape Hatches 裏面每個章節都有一些小挑戰,可以的話盡可能完成它。我覺得裡面的題目很多都是在實際開發時會碰到的問題,全部都寫完的話很有幫助!

總結

React 官方文件寫的很用心,除了認識到 React 的各種 pattern 之外,也有很多現代前端開發必須要注意的事情。**而且全部都是免費的!**由於篇幅的關係,我只挑一些我覺得蠻重要的地方來講,但是裡頭每個章節都很精彩,推薦給大家。


上一篇
[Day16] 驅動現代社會的半導體 (2) - 製程與應用
下一篇
[Day18] 充電器與背後的原理
系列文
從電子元件到傅立葉轉換 - 那些我有興趣的主題30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言